package com.gt.wxpay.cashier.payChannel.handler;

import com.gt.wxpay.cashier.common.LocalTimeUtils;
import com.gt.wxpay.cashier.domain.dto.QueryRefundDto;
import com.gt.wxpay.cashier.domain.model.CashierPayment;
import com.gt.wxpay.cashier.domain.model.PayTypeEnum;
import com.gt.wxpay.cashier.domain.model.RefundStatusEnum;
import com.gt.wxpay.cashier.payChannel.application.WxConfigService;
import com.gt.wxpay.cashier.payChannel.callback.vo.WxPayRespDto;
import com.gt.wxpay.cashier.payChannel.domain.model.PayChannel;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.cipher.Signer;
import com.wechat.pay.java.core.util.NonceUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.time.LocalDateTime;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Service
public class JsapiPrepayHandler {

    @Autowired
    private WxConfigService wxConfigService;

    /**
     * 微信支付JSAPI下单
     *
     * @param cashierPayment 订单
     * @return WxPayRespDto
     */
    public WxPayRespDto handlePrepay(CashierPayment cashierPayment) {
        try {
            // 微信支付渠道配置
            Config wxConfig = wxConfigService.getWxConfig(PayTypeEnum.WECHAT);
            String wxCallBackUrl = wxConfigService.getWxCallBackUrl(false);
            PayChannel payChannel = wxConfigService.getPayChannel(null);
            // 设置回渠道Id
            cashierPayment.setPayChannelId(String.valueOf(payChannel.getMid()));
            // 渠道手续费
//            cashierPayment.setChannelServiceFee(payChannel.getFee());
            // 下单请求参数
            PrepayRequest request = new PrepayRequest();
            // 微信appId
            request.setAppid(cashierPayment.getAppId());
            // 微信商户id||是微信支付收款的商户ID--来自渠道{不是业务商户}
            request.setMchid(wxConfig.createCredential().getMerchantId());
            // 商品描述
            request.setDescription(cashierPayment.getDescription());
            // 设置订单超时时间
            request.setTimeExpire(LocalDateTime.now().plusMinutes(30).toString() + "+08:00");
            // 回调地址，仅支持https||微信侧回调收银台的地址
            request.setNotifyUrl(wxCallBackUrl);
            // 支付订单号||收银台侧的流水号||不是业务系统的流水号
            request.setOutTradeNo(cashierPayment.getPaymentNo());
            // 订单金额
            Amount amount = new Amount();
            amount.setTotal(cashierPayment.getAmount().intValue());
            amount.setCurrency(cashierPayment.getCurrencyType());
            request.setAmount(amount);
            // 支付环境
            request.setAttach("收银台:" + cashierPayment.getPaymentNo() + ";业务侧:" + cashierPayment.getOrderNo());
            // 付款人
            Payer payer = new Payer();
            // 用户id
            payer.setOpenid(cashierPayment.getOpenId());
            request.setPayer(payer);
            // 支付环境
            if (StringUtils.isNotBlank(cashierPayment.getClientIp())) {
                SceneInfo sceneInfo = new SceneInfo();
                sceneInfo.setPayerClientIp(cashierPayment.getClientIp());
                request.setSceneInfo(sceneInfo);
            }
            // 使用jsapi方式请求
            JsapiServiceExtension service =
                    new JsapiServiceExtension.Builder()
                            .config(wxConfig)
                            .signType("RSA") // 不填默认为RSA
                            .build();
            // 发起下单
            PrepayWithRequestPaymentResponse prepayResp = service.prepayWithRequestPayment(request);
            if (prepayResp == null || StringUtils.isEmpty(prepayResp.getPackageVal())) {
                log.error("发起付款失败, 返回的预付款订单号为空");
                throw new RuntimeException("发起付款失败, 返回的预付款订单号为空");
            }
            // 下单成功---组装数据
            return this.buildWxPayRespVo(cashierPayment, prepayResp);
        } catch (Exception e) {
            log.error("生成签名失败：{}", e.getMessage(), e);
            throw new RuntimeException("生成签名失败");
        }
    }

    /**
     * 微信发送--小程序下单返回字段填充
     *
     * @param cashierPayment cashierPayment
     * @param prepayResp     prepayResp
     * @return WxPayRespDto
     * @throws Exception Exception
     * @author GuoTong
     */
    public WxPayRespDto buildWxPayRespVo(CashierPayment cashierPayment, PrepayWithRequestPaymentResponse prepayResp) throws Exception {
        return new WxPayRespDto()
                .setPrepayId(prepayResp.getPackageVal())
                .setPaymentNo(cashierPayment.getPaymentNo())
                .setNonceStr(prepayResp.getNonceStr())
                .setTimeStamp(prepayResp.getTimeStamp())
                .setPaySign(prepayResp.getPaySign())
                .setSignType("RSA");
    }

    /**
     * 手动封装---吴爽
     *
     * @param cashierPayment cashierPayment
     * @param prepayId       prepayId
     * @param wxConfig       wxConfig
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws URISyntaxException
     */
    public WxPayRespDto getWxPayRespVo(CashierPayment cashierPayment, String prepayId, Config wxConfig) throws Exception {

        Long timeStamp = System.currentTimeMillis() / 1000;
        String nonceStr = NonceUtil.createNonce(32);
        // 生成签名
        String signatureStr = Stream.of(cashierPayment.getAppId(),
                        String.valueOf(timeStamp),
                        nonceStr,
                        prepayId)
                .collect(Collectors.joining("\n", "", "\n"));
        Signer signer = wxConfig.createSigner();
        String sign = signer.sign(signatureStr).getSign();

        return new WxPayRespDto()
                .setPrepayId(prepayId)
                .setPaymentNo(cashierPayment.getPaymentNo())
                .setNonceStr(nonceStr)
                .setTimeStamp(timeStamp.toString())
                .setPaySign(sign)
                .setSignType(signer.getAlgorithm());
    }

    /**
     * 商户可以通过查询订单接口主动查询订单状态，完成下一步的业务逻辑。
     * <p>
     * 查询订单可通过微信支付订单号 (opens new window)和商户订单号 (opens new window)两种方式查询。
     * <p>
     * 需要调用查询接口的情况：
     * <p>
     * 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知。
     * 调用支付接口后，返回系统错误或未知交易状态情况。
     * 调用付款码支付API，返回USERPAYING的状态。
     * 调用关单或撤销接口API之前，需确认支付状态。
     *
     * @param cashierPayment CashierPayment
     * @return WxPayRespDto
     */
    public Transaction queryOrderInfo(CashierPayment cashierPayment) {
        String payChannelId = cashierPayment.getPayChannelId();
        Config wxConfig = wxConfigService.getWxConfig(Long.parseLong(payChannelId));
        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        request.setMchid(wxConfig.createCredential().getMerchantId());
        request.setOutTradeNo(cashierPayment.getPaymentNo());
        JsapiServiceExtension service =
                new JsapiServiceExtension.Builder()
                        .config(wxConfig)
                        .signType("RSA") // 不填默认为RSA
                        .build();
        Transaction transaction = null;
        try {
            transaction = service.queryOrderByOutTradeNo(request);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return transaction;
    }

    /**
     * 关闭订单，以下情况需要调用关单接口：
     * <p>
     * 商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付；
     * 系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
     *
     * @param cashierPayment cashierPayment
     */
    public void closePayment(CashierPayment cashierPayment) {
        CloseOrderRequest request = new CloseOrderRequest();
        Config wxConfig = wxConfigService.getWxConfig(Long.parseLong(cashierPayment.getPayChannelId()));
        // 使用微信侧的商户
        request.setMchid(wxConfig.createCredential().getMerchantId());
        // 商户系统内部订单号
        request.setOutTradeNo(cashierPayment.getPaymentNo());
        JsapiService service = new JsapiService.Builder().config(wxConfig).build();
        service.closeOrder(request);
    }

    /**
     * 当交易发生之后一段时间内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付款退还给买家，
     * <p>
     * 微信支付将在收到退款请求并且验证成功之后，按照退款规则将支付款按原路退到买家帐号上。
     *
     * @param cashierPayment cashierPayment
     * @return Refund
     */
    @SuppressWarnings("unchecked,deprecation,unused")
    public Refund refundOrderV3(CashierPayment cashierPayment) {
        Config wxConfig = wxConfigService.getWxConfig(Long.parseLong(cashierPayment.getPayChannelId()));
        String wxCallBackUrl = wxConfigService.getWxCallBackUrl(true);
        RefundService service = new RefundService.Builder().config(wxConfig).build();
        CreateRequest request = new CreateRequest();
        // 退款收银台订单号
        request.setOutRefundNo(cashierPayment.getPaymentNo());
        // 微信支付订单号(原支付渠道流水号)
        request.setTransactionId(cashierPayment.getChannelFlowNo());
        // 使用可用余额退款 ===== request.setFundsAccount(ReqFundsAccount.AVAILABLE);
        request.setNotifyUrl(wxCallBackUrl);
        // 退款金额
        AmountReq amountReq = new AmountReq();
        amountReq.setTotal(cashierPayment.getAmount());
        amountReq.setRefund(cashierPayment.getRefundAmount());
        amountReq.setCurrency(StringUtils.isEmpty(cashierPayment.getCurrencyType()) ? "CNY" : cashierPayment.getCurrencyType());
        request.setAmount(amountReq);
        // 发起退款
        Refund refund = service.create(request);
        log.error("退款处理--微信结果：{}", refund.toString());
        if (refund.getStatus() == Status.SUCCESS ||
                refund.getStatus() == Status.PROCESSING) {
            log.info("发起退款成功：" + cashierPayment.getPaymentNo());
            return refund;
        } else {
            log.error("发起退款失败：{}", refund.getStatus());
            throw new RuntimeException("发起退款失败");
        }
    }

    public QueryRefundDto queryRefundOrderV3(CashierPayment refundOrder) {
        Config wxConfig = wxConfigService.getWxConfig(Long.parseLong(refundOrder.getPayChannelId()));
        RefundService service = new RefundService.Builder().config(wxConfig).build();
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        // 【商户退款单号】 商户系统内部的退款单号
        request.setOutRefundNo(refundOrder.getPaymentNo());
        Refund refund = service.queryByOutRefundNo(request);
        log.error("退款订单查询--微信结果：{}", refund.toString());
        // 获取系统的退款单状态
        switch (refund.getStatus()) {
            case SUCCESS:
                log.info("退款成功......");
                refundOrder.setStatus(RefundStatusEnum.SUCCESS.getCode());
                refundOrder.setChannelRefundSuccessTime(LocalTimeUtils.dealDateFormatWithWx(refund.getSuccessTime()));
                break;
            case CLOSED:
                log.info("退款已关闭......");
                refundOrder.setStatus(RefundStatusEnum.CLOSED.getCode());
                break;
            case PROCESSING:
                log.info("退款处理中......");
                refundOrder.setStatus(RefundStatusEnum.PROCESSING.getCode());
                break;
            case ABNORMAL:
                log.error("退款异常......");
                refundOrder.setStatus(RefundStatusEnum.ABNORMAL.getCode());
                break;
            default:
                break;
        }
        refundOrder.setChannelRefundStatus(refund.getStatus().name());
        return buildQueryRefundDto(refund, refundOrder);
    }

    private QueryRefundDto buildQueryRefundDto(Refund refund, CashierPayment refundOrder) {
        QueryRefundDto queryRefundDto = new QueryRefundDto();
        queryRefundDto.setRefundNo(refund.getOutRefundNo());
        queryRefundDto.setRefundTime(LocalTimeUtils.dealFormatWithWx(refund.getSuccessTime()));
        queryRefundDto.setCreateTime(LocalTimeUtils.dealFormatWithWx(refund.getCreateTime()));
        queryRefundDto.setRefundAmount(refund.getAmount().getRefund());
        queryRefundDto.setAmount(refund.getAmount().getTotal());
        switch (refund.getStatus().name()) {
            case "SUCCESS":
                queryRefundDto.setStatus(1);
                break;
            case "CLOSED":
                queryRefundDto.setStatus(2);
                break;
            case "ABNORMAL":
                queryRefundDto.setStatus(4);
                break;
            default:
                queryRefundDto.setStatus(3);
                break;
        }
        return queryRefundDto;
    }
}
